package com.me.myFramework.myAop.aop130;

import com.me.myFramework.myAop.aop130.advice.Advice;
import com.me.myFramework.myAop.aop130.advice.method.AfterReturningAdvice;
import com.me.myFramework.myAop.aop130.advice.method.MethodBeforeAdvice;
import com.me.myFramework.myAop.aop130.intercept.method.AfterReturningAdviceInterceptor;
import com.me.myFramework.myAop.aop130.intercept.method.MethodBeforeAdviceInterceptor;
import com.me.myFramework.myAop.aop130.intercept.method.MethodInterceptor;
import org.springframework.aop.Pointcut;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

/**
 * 迭代 AOP-120：使用 代理套代理的方式 简单实现多切面。
 * TODO 此实现中，每个切面都必须生成一个代理（不可忽略）
 */
public class ProxyFactory {

    private Object target;

    private List<PointcutAdvisor> advisors = new ArrayList<>();

    public ProxyFactory(Object target) {
        this.target = target;
    }

    /**
     * 代理套代理实现的多切面会针对每个切面生成一个代理类，存在如下问题：
     *  1，性能不高（创建代理、加载代理、初始化、实例化）。
     *  2，会创建大量 class信息
     *
     * TODO 需要调整逻辑只生成一个代理类，然后在代理类中循环执行 advice
     *
     * @return
     */
    public Object getProxy() {
        Object proxy = null;

        for (int i = this.advisors.size() - 1; i >= 0; i--) {
            PointcutAdvisor advisor = advisors.get(i);
            proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
                private final Object curTarget = target;

                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    Pointcut pointcut = advisor.getPointcut();
                    Class<?> targetClass = target.getClass();
                    if (pointcut.getClassFilter().matches(targetClass) && pointcut.getMethodMatcher().matches(method, targetClass)) {
                        MethodInterceptor interceptor = advice2Interceptor(advisor.getAdvice());
                        return interceptor.invoke(method, args, curTarget);
                    }
                    else {
                        return method.invoke(curTarget, args);
                    }
                }
            });
            this.target = proxy;
        }

        return proxy;
    }

    private List<MethodInterceptor> getInterceptors(Method method, Class<?> targetClass) {
        List<MethodInterceptor> interceptorList = new ArrayList<>(this.advisors.size());
        for (PointcutAdvisor advisor : advisors) {
            Pointcut pointcut = advisor.getPointcut();
            if (pointcut.getClassFilter().matches(targetClass) && pointcut.getMethodMatcher().matches(method, targetClass)) {
                Advice advice = advisor.getAdvice();
                if (advice instanceof MethodBeforeAdvice) {
                    interceptorList.add(new MethodBeforeAdviceInterceptor((MethodBeforeAdvice) advice));
                }
                else if (advice instanceof AfterReturningAdvice) {
                    interceptorList.add(new AfterReturningAdviceInterceptor((AfterReturningAdvice) advice));
                }
                else {
                    throw new RuntimeException("未知的 advice 类型");
                }
            }
        }

        return interceptorList;
    }

    /**
     * 根据 切面类生成 MethodInterceptor
     *
     * @param advice
     * @return
     */
    private MethodInterceptor advice2Interceptor(Advice advice) {
        if (advice instanceof MethodBeforeAdvice) {
            return new MethodBeforeAdviceInterceptor((MethodBeforeAdvice) advice);
        }
        else if (advice instanceof AfterReturningAdvice) {
            return new AfterReturningAdviceInterceptor((AfterReturningAdvice) advice);
        }
        else {
            throw new RuntimeException("未知的 advice 类型");
        }
    }

    public Object getTarget() {
        return target;
    }

    public void addAdvisor(PointcutAdvisor advisor) {
        this.advisors.add(advisor);
    }

    public List<PointcutAdvisor> getAdvisors() {
        return advisors;
    }

    public void setAdvisors(List<PointcutAdvisor> advisors) {
        this.advisors = advisors;
    }
}
